import 'dart:math';

import 'package:flutter/material.dart';

import 'shimmer.dart';

/// github地址：https://github.com/badjio/skeletons
class DxSkeleton extends StatefulWidget {
  const DxSkeleton({
    Key? key,
    required this.isLoading,
    required this.skeleton,
    this.builder,
    this.child,
    this.shimmerGradient,
    this.darkShimmerGradient,
    this.duration,
    this.themeMode,
  }) : super(key: key);

  final bool isLoading;
  final Widget skeleton;
  final Widget Function()? builder;

  final Widget? child;
  final LinearGradient? shimmerGradient;
  final LinearGradient? darkShimmerGradient;
  final Duration? duration;
  final ThemeMode? themeMode;

  @override
  State<DxSkeleton> createState() => _DxSkeletonState();
}

class _DxSkeletonState extends State<DxSkeleton> {
  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(
      duration: const Duration(milliseconds: 150),
      child: widget.isLoading
          ? DxShimmerWidget(
              shimmerGradient: widget.shimmerGradient,
              darkShimmerGradient: widget.darkShimmerGradient,
              duration: widget.duration,
              themeMode: widget.themeMode,
              child: _DxSkeletonWidget(isLoading: widget.isLoading, skeleton: widget.skeleton),
            )
          : (widget.builder?.call() ?? widget.child ?? const SizedBox.shrink()),
    );
  }
}

class _DxSkeletonWidget extends StatefulWidget {
  const _DxSkeletonWidget({
    Key? key,
    required this.isLoading,
    required this.skeleton,
  }) : super(key: key);

  final bool isLoading;
  final Widget skeleton;

  @override
  State<_DxSkeletonWidget> createState() => __SkeletonWidgetState();
}

class __SkeletonWidgetState extends State<_DxSkeletonWidget> {
  Listenable? _shimmerChanges;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (_shimmerChanges != null) {
      _shimmerChanges!.removeListener(_onShimmerChange);
    }
    _shimmerChanges = DxShimmer.of(context)?.shimmerChanges;
    if (_shimmerChanges != null) {
      _shimmerChanges!.addListener(_onShimmerChange);
    }
  }

  @override
  void dispose() {
    _shimmerChanges?.removeListener(_onShimmerChange);
    super.dispose();
  }

  void _onShimmerChange() {
    if (widget.isLoading) {
      setState(() {
        // update the shimmer painting.
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final shimmer = DxShimmer.of(context)!;
    if (!shimmer.isSized) {
      return const SizedBox.shrink();
    }
    final shimmerSize = shimmer.size;
    final gradient = shimmer.currentGradient;

    if (context.findRenderObject() == null) return const SizedBox.shrink();

    final offsetWithinShimmer = shimmer.getDescendantOffset(
      descendant: context.findRenderObject() as RenderBox,
    );

    return ShaderMask(
      blendMode: BlendMode.srcATop,
      shaderCallback: (bounds) {
        return gradient.createShader(
          Rect.fromLTWH(
            -offsetWithinShimmer.dx,
            -offsetWithinShimmer.dy,
            shimmerSize.width,
            shimmerSize.height,
          ),
        );
      },
      child: widget.skeleton,
    );
  }
}

/// github地址：https://github.com/badjio/skeletons
class DxSkeletonItem extends StatelessWidget {
  final Widget child;
  const DxSkeletonItem({Key? key, required this.child}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    if (DxShimmer.of(context) == null) {
      return DxShimmerWidget(child: _DxSkeletonWidget(isLoading: true, skeleton: child));
    }
    return child;
  }
}

class DxSkeletonAvatar extends StatelessWidget {
  final DxSkeletonAvatarStyle style;
  const DxSkeletonAvatar({
    Key? key,
    this.style = const DxSkeletonAvatarStyle(),
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DxSkeletonItem(
      child: Padding(
        padding: style.padding,
        child: LayoutBuilder(
          builder: (context, constraints) {
            return Container(
              width: ((style.randomWidth != null && style.randomWidth!) ||
                      (style.randomWidth == null && (style.minWidth != null && style.maxWidth != null)))
                  ? doubleInRange(style.minWidth ?? ((style.maxWidth ?? constraints.maxWidth) / 3),
                      style.maxWidth ?? constraints.maxWidth)
                  : style.width,
              height: ((style.randomHeight != null && style.randomHeight!) ||
                      (style.randomHeight == null && (style.minHeight != null && style.maxHeight != null)))
                  ? doubleInRange(style.minHeight ?? ((style.maxHeight ?? constraints.maxHeight) / 3),
                      style.maxHeight ?? constraints.maxHeight)
                  : style.height,
              decoration: BoxDecoration(
                color: Theme.of(context).colorScheme.surface,
                shape: style.shape,
                borderRadius:
                    style.shape != BoxShape.circle ? BorderRadius.all(Radius.circular(style.borderRadius)) : null,
              ),
            );
          },
        ),
      ),
    );
  }
}

class DxSkeletonLine extends StatelessWidget {
  final DxSkeletonLineStyle style;
  const DxSkeletonLine({
    Key? key,
    this.style = const DxSkeletonLineStyle(),
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DxSkeletonItem(
      child: Align(
        alignment: style.alignment,
        child: Padding(
          padding: style.padding,
          child: LayoutBuilder(
            builder: (context, constraints) {
              return Container(
                width: ((style.randomLength != null && style.randomLength!) ||
                        (style.randomLength == null && (style.minLength != null && style.maxLength != null)))
                    ? doubleInRange(style.minLength ?? ((style.maxLength ?? constraints.maxWidth) / 3),
                        style.maxLength ?? constraints.maxWidth)
                    : style.width,
                height: style.height,
                decoration: BoxDecoration(
                  color: Theme.of(context).colorScheme.surface,
                  borderRadius: BorderRadius.all(Radius.circular(style.borderRadius)),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

class DxSkeletonParagraph extends StatelessWidget {
  final DxSkeletonParagraphStyle style;

  const DxSkeletonParagraph({
    Key? key,
    this.style = const DxSkeletonParagraphStyle(),
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DxSkeletonItem(
      child: Padding(
        padding: style.padding,
        child: Column(
          children: [
            for (var i = 1; i <= style.lines; i++) ...[
              DxSkeletonLine(style: style.lineStyle),
              if (i != style.lines) SizedBox(height: style.spacing)
            ]
          ],
        ),
      ),
    );
  }
}

class DxSkeletonListTile extends StatelessWidget {
  final bool hasLeading;
  final DxSkeletonAvatarStyle? leadingStyle;
  final DxSkeletonLineStyle? titleStyle;
  final bool hasSubtitle;
  final DxSkeletonLineStyle? subtitleStyle;
  final EdgeInsets? padding;
  final double? contentSpacing;
  final double? verticalSpacing;
  final Widget? trailing;

  // final SkeletonListTileStyle style;

  const DxSkeletonListTile({
    Key? key,
    this.hasLeading = true,
    this.leadingStyle,
    this.titleStyle = const DxSkeletonLineStyle(padding: EdgeInsets.zero, height: 22),
    this.subtitleStyle = const DxSkeletonLineStyle(height: 16, padding: EdgeInsets.only(right: 32)),
    this.hasSubtitle = true,
    this.padding = const EdgeInsets.symmetric(vertical: 8),
    this.contentSpacing = 8,
    this.verticalSpacing = 8,
    this.trailing,
  }) : super(key: key);
  // : assert(height >= lineHeight + spacing + (padding?.vertical ?? 16) + 2);

  @override
  Widget build(BuildContext context) {
    return DxSkeletonItem(
      child: Padding(
        padding: padding ?? EdgeInsets.zero,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            if (hasLeading)
              DxSkeletonAvatar(style: leadingStyle ?? const DxSkeletonAvatarStyle(shape: BoxShape.circle)),
            SizedBox(width: contentSpacing),
            Expanded(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  DxSkeletonLine(style: titleStyle ?? const DxSkeletonLineStyle()),
                  if (hasSubtitle) ...[
                    SizedBox(height: verticalSpacing),
                    DxSkeletonLine(style: subtitleStyle ?? const DxSkeletonLineStyle()),
                  ]
                ],
              ),
            ),
            if (trailing != null) trailing!,
          ],
        ),
      ),
    );
  }
}

class DxSkeletonListView extends StatelessWidget {
  final Widget? item;
  final Widget Function(BuildContext, int)? itemBuilder;
  final int itemCount;
  final bool scrollable;
  final EdgeInsets? padding;
  final double spacing;

  const DxSkeletonListView({
    Key? key,
    this.item,
    this.itemBuilder,
    this.itemCount = 20,
    this.scrollable = false,
    this.padding = const EdgeInsets.symmetric(horizontal: 15),
    this.spacing = 8,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return DxSkeletonItem(
      child: ListView.builder(
        shrinkWrap: true,
        padding: padding,
        physics: scrollable ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
        itemCount: itemCount,
        itemBuilder: itemBuilder ??
            (_, __) => item ?? DxSkeletonListTile(padding: EdgeInsets.only(bottom: spacing), hasSubtitle: true),
      ),
    );
  }
}

double doubleInRange(num start, num end) => Random().nextDouble() * (end - start) + start;

class DxSkeletonAvatarStyle {
  final EdgeInsets padding;
  final double borderRadius;
  final BoxShape shape;
  final double? width;
  final double? height;
  final bool? randomWidth;
  final double? minWidth;
  final double? maxWidth;
  final bool? randomHeight;
  final double? minHeight;
  final double? maxHeight;

  const DxSkeletonAvatarStyle({
    this.padding = EdgeInsets.zero,
    this.borderRadius = 4,
    this.shape = BoxShape.rectangle,
    this.width = 48,
    this.height = 48,
    this.randomWidth,
    this.minWidth,
    this.maxWidth,
    this.randomHeight,
    this.minHeight,
    this.maxHeight,
  })  : assert(minWidth == null || (minWidth > 0 && (maxWidth == null || maxWidth > minWidth))),
        assert(maxWidth == null || (maxWidth > 0 && (minWidth == null || minWidth < maxWidth))),
        assert(minHeight == null || (minHeight > 0 && (maxHeight == null || maxHeight > minHeight))),
        assert(maxHeight == null || (maxHeight > 0 && (minHeight == null || minHeight < maxHeight)));
}

class DxSkeletonLineStyle {
  final EdgeInsets padding;
  final double borderRadius;
  final Alignment alignment;
  final double? minLength;
  final double? maxLength;
  final bool? randomLength;
  final double? width;
  final double? height;

  const DxSkeletonLineStyle({
    this.padding = EdgeInsets.zero,
    this.borderRadius = 2,
    this.alignment = Alignment.centerLeft,
    this.minLength,
    this.maxLength,
    this.randomLength,
    this.width = double.infinity,
    this.height = 18,
  })  : assert(minLength == null || (minLength > 0 && (maxLength == null || maxLength > minLength))),
        assert(maxLength == null || (maxLength > 0 && (minLength == null || minLength < maxLength)));
}

class DxSkeletonParagraphStyle {
  final int lines;
  final EdgeInsets padding;
  final double spacing;
  final DxSkeletonLineStyle lineStyle;

  const DxSkeletonParagraphStyle({
    this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
    this.lines = 3,
    this.spacing = 12,
    this.lineStyle = const DxSkeletonLineStyle(),
  });
}

class DxSkeletonListTileStyle {
  final EdgeInsets? padding;
  final double? contentSpacing;
  final double? verticalSpacing;
  final bool hasLeading;
  final DxSkeletonAvatarStyle? leadingStyle;
  final DxSkeletonLineStyle? titleStyle;
  final bool hasSubtitle;
  final DxSkeletonLineStyle? subtitleStyle;

  const DxSkeletonListTileStyle({
    this.padding = const EdgeInsets.symmetric(vertical: 8),
    this.contentSpacing = 8,
    this.verticalSpacing = 8,
    this.hasLeading = true,
    this.leadingStyle,
    this.titleStyle = const DxSkeletonLineStyle(height: 22),
    this.subtitleStyle = const DxSkeletonLineStyle(height: 16, padding: EdgeInsets.only(right: 32)),
    this.hasSubtitle = false,
  });
}
